using System;
using System.Collections.Generic;
namespace Framework
{
    [UnityEngine.Scripting.Preserve]
    public class Fsm
    {
        public static readonly string EventActionFinish = "Finished";
        public Object Owner { get; set; }
        private readonly Dictionary<string, FsmState> states;
        private FsmState currentState;
        private FsmState lastState;
        private bool initialized;
        private FsmState switchToState;
        private bool _force;
        private FsmTransition nextTransition;
        private FsmTransition currentTransition;
        private readonly Queue<string> _event;
        private readonly object sign;
        public Fsm()
        {
            states = new Dictionary<string, FsmState>();
            currentState = null;
            sign = states;
            _event = new Queue<string>();
        }
        public FsmState Add(string state)
        {
            if (state == null)
            {
                throw new Exception("FSM states is invalid.");
            }
            if (states.ContainsKey(state))
            {
                throw new Exception(string.Format("state '{0}' is already exist.", state));
            }
            var item = new FsmState();
            item.SetName(state);
            states.Add(item.Name, item);
            return item;
        }
        public FsmState CurrentState
        {
            get
            {
                return currentState;
            }
        }
        public FsmState LastState
        {
            get
            {
                return lastState;
            }
        }
        public bool Initialized
        {
            get
            {
                return this.initialized;
            }
        }
        public void Start(string name)
        {
            if (this.Initialized)
            {
                return;
            }
            this.initialized = true;
            foreach (var state in states)
            {
                foreach (var transition in state.Value.Transitions)
                {
                    var toState = transition.ToState;
                    if (string.IsNullOrEmpty(toState)) continue;
                    transition.ToFsmState = GetState(toState);
                }
                state.Value.InitData(this);
            }
            ForceChange(name);
        }
        public void Stop()
        {
            currentState?.Exit(this.sign);
            currentTransition?.ToFsmState.Exit(this.sign);
            switchToState = null;
            currentTransition = null;
            nextTransition = null;
            currentState = null;
            this.initialized = false;
        }
        public void SendEvent(string name)
        {
            if (_event.Count > 0)
            {
                var p = _event.Peek();
                if (p.Equals(name)) return;
            }
            _event.Enqueue(name);
        }
        public void ForceChange(string stateName)
        {
            switchToState = GetState(stateName);
            _force = true;
            _event.Clear();
        }
        private FsmState GetState(string name)
        {
            if (states.TryGetValue(name, out FsmState state))
            {
                return state;
            }
            return null;
        }
        public void Update(float deltaTime)
        {
            if (_event.Count > 0 && nextTransition == null && currentTransition == null && switchToState == null)
            {
                var name = _event.Dequeue();
                if (currentState != null)
                {
                    if (currentState.Name != name)
                    {
                        var transition = currentState.GetTransition(name);
                        if (transition != null && transition.ToFsmState != currentState)
                        {
                            nextTransition = transition;
                            switchToState = null;
                        }
                    }
                }
            }
            if (switchToState != null)
            {
                var toState = switchToState;
                switchToState = null;
                currentTransition = null;
                nextTransition = null;
                if (_force || currentState != toState)
                {
                    lastState = currentState;
                    currentState?.Exit(this.sign);
                    currentState = toState;
                    currentState?.Enter(this.sign);
                }
                _force = false;
            }
            if (nextTransition != null)
            {
                var transition = nextTransition;
                nextTransition = null;
                currentTransition = transition;
                currentTransition.Enter();
            }
            if (currentTransition != null)
            {
                currentTransition.Update(deltaTime);
                var p = currentTransition.Progress;
                if (p >= 1f)
                {
                    lastState = currentState;
                    currentState.Exit(this.sign);
                    currentState = currentTransition.ToFsmState;

                    currentState.Enter(sign);
                    currentTransition = null;
                    return;
                }
                currentTransition.ToFsmState.Update(deltaTime, p, this.sign);
                currentState.Update(deltaTime, 1 - p, this.sign);
                return;
            }
            if (currentState == null)
            {
                return;
            }
            currentState.Update(deltaTime, 1, this.sign);
        }
    }
}